연산자 오버로딩 (대입연산자, 전위-후위연산자)

이 노트는 C++ 연산자 오버로딩의 전역/멤버 함수 형태, 대입 연산자 오버로딩, 그리고 전위·후위 증가 연산자 오버로딩을 정리한다. 대입 연산자를 오버로딩할 때 클래스에 포인터 멤버가 있으면 컴파일러가 자동 생성하는 기본 대입은 얕은 복사라 문제가 되므로 직접 깊은 복사를 구현해야 하며, 자원을 그대로 옮기고 원본을 비우는 이동 대입 연산자로 성능을 아낄 수도 있다는 점을 예제로 보여준다.

  1. 연산자 오버로딩 형태
  2. 대입 연산자             
  3. 전위/후위 연산자

연산자 오버로딩 형태

[반환값]operator연산자
ex) int operator+(const int& number)

int b = (int)a + 2;

  • 전역 연산자든 멤버함수 연산자 오버로딩이든 사용자 정의 연산자 정의이기에 클래스를 통해서 이뤄짐
    • int operator(int a, int b) {} // 컴파일러 오류
    • 기본 타입에 대한 전역 연산자 오버로딩의 경우 표준 라이브러리 호환성 유지를 위해 오류를 내보냄
  • 전역 연산자 오버로딩
    • [반환값]operator[연산자]([인자1], [인자2])
      • 인자1 : 왼쪽 피연산자
      • 인자2 : 오른쪽 피연산자
        • ex) 반환값 = a(왼쪽 피연산자) + b (오른쪽 피연산자)
          • 인자1 : a , 인자2 : b
  • 멤버함수 연산자 오버로딩
    • 해당 클래스 자기자신을 기준으로 인자값을 연산함
      • 왼쪽 피연산자가 자기자신이 아닌경우 -> 전역 연산자 오버로딩으로 매개변수 위치 설정
    • [반환값]operator연산자
      • 왼쪽피연산자는 자기자신

        • 클래스 자기자신 지칭 : *this
      • 인자 : 오른쪽 피연산자

    • ex) 반환값 = a(왼쪽 피연산자) + b (오른쪽 피연산자);
      • a : 클래스 자기 자신
      • b : 더해질 값 (클래스 , 상수 …)
class Position
{
public:
    //(0)
    //복사 대입 연산자
    Position& operator= (const Position& arg)
    {
	    pos_x = arg.pos_x;
	    pos_y = arg.pos_y;
 
	    return *this;
    }
    
	Position operator+ (const Position& pos2)
	{
		Position pos;
		pos.pos_x = pos_x + pos2.pos_x;
		pos.pos_y = pos_y + pos2.pos_y;
		return pos;
	}
	
    // (1)
	Position operator+ (int arg)
	{
		Position pos;
		pos.pos_x = pos_x + arg;
		pos.pos_y = pos_y + arg;
		return pos;
	}
public:
	int pos_x = 0;
	int pos_y = 0;
};
 
// 전역 연산자 오버로딩 (클래스 내부 (1) oprator+와 동일의미)
//Position operator+ (const Position a, int b)
//{
//	Position c;
//	c.pos_x = a.pos_x + b;
//	return c;
//}
 
//(2)
//전역 연산자 오버로딩
Position operator+ (int a, const Position & b)
{
	Position ret;
 
	ret.pos_x = b.pos_x + a;
	ret.pos_y = b.pos_y + a;
 
	return ret;
}
 
int main()
{
    int a =1;
    Position pos;
    Position pos2;
 
    pos = (pos + 1); // pos+1 :(1)연산 ,  pos = (pos+1) : (0)연산
    pos = (1 + pos); // 1+pos :(2)연산 ,  pos = (1+pos) : (0)연산
 
}

대입 연산자 오버로딩

  • 대입 연산자 오버로딩
    • [반환값] operator([인자])
      • 인자 : 오른쪽 피연산자
      • ex) (Myclass) a = (int) b
        • a: 클래스 자기 자신
        • b: 대입할 오른쪽 매개변수
class Myclass
{
public:
    Myclass& operator=(int num)
    {
        _id = num; // 대입 연산 진행
        return *this; // 자기자신 return
    }
 
public:
    int _id = 0;
};
 
int main()
{
    Myclass m;
    m = 2; // 연산자 오버로딩
 
    return 0;
}
  • 복사 대입 연산자 오버로딩
    • [반환값] operator([인자])
      • 자기 자신을 인자로 받는경우 예외처리 필요
      • 대부분 인자로 같은 클래스를 받는 경우가 대부분
    • 기본적으로 얕은 복사가 이루어짐 (컴파일러가 자동으로 생성해줌)
      • 포인터일경우 주소값도 동일하게 복사됨 (주소를 그대로 복사하기에 문제가 될 수 있음)
      • 데이터도 일반적으로 복사됨
    • 포인터가 있을 경우, 해당 부분의 깊은 복사를 진행해야함
      • 깊은 복사를 하지 않을경우, 대입때 사용된 other 클래스 객체 값 변경시 같이 변경됨
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
 
class Myclass
{
public:
 
    Myclass(const char* str = "")
    {
        data = new char[strlen(str) + 1];
        strcpy(data, str);
    }
    
    // 일반적인 값 대입 연산
    Myclass& operator=(int num)
    {
        _id = num;
 
        return *this;
    }
    
    // 복사대입연산자 ( 깊은 복사 )
    Myclass& operator=(const Myclass& m)
    {
        // 자기자신 대입 방지
        if (this == &m)
            return *this;
 
        // 주소값 복사되는 얕은복사 방지 (깊은복사 진행)
        _id = m._id;
        data = new char[strlen(m.data) + 1];
        strcpy(data, m.data);
 
        //data = m.data;
        return *this;
    }
    
    // 복사대입 연산자 (얕은 복사)
    /*
     Myclass& operator=(const Myclass& m)
    {
        // 자기자신 대입 방지
        if (this == &m)
            return *this;
 
        _id = m._id;
        data = m.data; // 얕은복사 진행 (주소 + 데이터)
        
        return *this;
    }
    */
 
    void setData(const char* _data)
    {
        strcpy(data,_data);
    }
 
public:
    int _id = 0;
    char* data;
};
 
int main()
{
    Myclass m("Hello");
    Myclass m2("Bye");
 
    m2 = 2;
    m = m2;
    
    // 얕은 복사의 경우
    // m.data =="qqqq"
    // m2.data == "qqqq" 로 변경됨
    m2.setData("qqqq");
 
    //깊은 복사의 경우
    // m2.data == "qqqq" 변경
    // m.data 변경 없음
 
    
    
    return 0;
}
  • 이동 대입 연산자
    • 인자로 받은 값을 연산하는 클래스로 값을 옮기고 초기화 해주는 역할
    • Other 데이터 -> 자기자신
      • 복사대입연산자와 다르게 주소를 그대로 사용해도 되므로 얕은복사 진행
      • 리소스 절약 가능

Myclass
{

  MyString& operator = (MyString&& other) noexcept
  {
         // 자기자신일 경우 패스
         if (this== &other) 
              return*this;

         // 기존 자원 해제
         delete[] data;
     
          // data 옮기기
         data = other.data;
     
         // 다른 객체의 자원 포인터를 nullptr로 설정
          other.data = nullptr;
     
          return *this;
     }

public:
    char *data;
}

전위 / 후위 연산자 오버로딩

  • 전위 연산자 오버로딩
    • ++a 형태
    • ”++” 연산을 진행한 후 바로 적용
    • [반환값] operator++() 으로 생성
class Position
{
public:
	// 전위형 (++a)
	Position& operator++()
	{
		pos_x++;
		pos_y++;
		return *this; // 연산 후 바로 적용
	}
 
public:
	int pos_x = 0;
	int pos_y = 0;
};
  • 후위 연산자 오버로딩
    • a++ 형태
    • ”++“연산은 이후에 적용되고, 원본값이 결과에 반환되어야함
    • [반환값] operator++(int) 으로 생성
class Position
{
public:
	// 후위형 (a++)
	Position operator++(int)
	{
        // 원본값 저장
		Position ret = *this; 
		
        // 1 증가
        pos_x++;
		pos_y++;
        
		// 원본값 return 
        return ret; 
	}
 
public:
	int pos_x = 0;
	int pos_y = 0;
};